import os
import random
import shutil
from turtledemo.penrose import start

import numpy as np
from moviepy import AudioFileClip, VideoFileClip
import pyJianYingDraft as draft

from combinations.config import OUTPUT_PATH, DRAFT_PATH
from combinations.conf import logger
from pyJianYingDraft import Font_type, trange, Text_style, Export_framerate, Export_resolution, Clip_settings, \
    Intro_type
import pandas as pd

from pyJianYingDraft.segment import Speed
from pyJianYingDraft.template_mode import Imported_media_track


# ==================== 工具函数 ====================

def load_files_from_dir(directory):
    """
    加载指定目录下的所有文件，并返回其全路径列表。

    Args:
        directory (str): 文件夹路径

    Returns:
        List[str]: 文件的全路径列表
    """
    logger.info(f"正在加载目录中的文件: {directory}")
    files = [os.path.join(directory, f) for f in os.listdir(directory)
             if os.path.isfile(os.path.join(directory, f))]
    logger.debug(f"共找到 {len(files)} 个文件")
    return files


def replace_bgm(script, bgm_file):
    """
    替换剪映草稿中的背景音乐素材。

    Args:
        script (draft.Script): 当前操作的脚本对象
        bgm_file (str): 新背景音乐文件路径
    """
    logger.info("开始替换 BGM...")
    bgm_material = draft.Audio_material(bgm_file)
    script.replace_material_by_name(material_name="if i got jesus.MP3", material=bgm_material)
    logger.info(f"替换【BGM】素材: if i got jesus.MP3 -> {bgm_file}")


def replace_voiceover(script, audio_path):
    """
    替换口播音频素材。

    Args:
        script (draft.Script): 当前操作的脚本对象
        audio_path (str): 新音频文件路径
    """
    logger.info("开始替换口播音频...")
    new_material = draft.Audio_material(audio_path)
    audio_track = script.get_imported_track(draft.Track_type.audio, index=0)
    script.replace_material_by_seg(track=audio_track, segment_index=0,
                                   material=new_material,
                                   handle_extend=[draft.Extend_mode.extend_tail])
    logger.info(f"替换【音频】素材: 口播音频.wav -> {audio_path}")


def replace_images(script, image_files):
    """
    替换剪映模板中的图片素材。

    Args:
        script (draft.Script): 当前操作的脚本对象
        image_files (List[str]): 图片文件路径列表
    """
    logger.info("开始替换图片素材...")

    # 定义原始图片名称格式（图片_1.png ~ 图片_20.png）
    original_names = [f"图片_{i}.png" for i in range(1, 21)]

    # 遍历每张新图片进行替换
    for idx, image_file in enumerate(image_files):
        original_name = original_names[idx]
        new_material = draft.Video_material(image_file)

        # 替换素材并获取新的 material_id
        script, material_id = script.replace_material_by_name(
            material_name=original_name,
            material=new_material
        )
        logger.info(f"替换【图片】素材: {original_name} -> {image_file}")
        logger.info(f"图片素材宽高: {new_material.width} x {new_material.height}")

        # 遍历所有导入轨道，查找对应 segment 并更新动画/速度等信息
        for track in script.imported_tracks:
            if not isinstance(track, Imported_media_track):
                continue  # 跳过非媒体轨道

            if not isinstance(track.segments, list):
                continue  # 跳过没有 segments 的轨道

            for segment in track.segments:
                if isinstance(segment, dict):  # 跳过字典类型
                    continue

                # 如果找到匹配的素材 ID
                if segment.material_id == material_id:
                    _update_segment_with_new_animation_and_speed(
                        script=script,
                        segment=segment,
                        new_material=new_material,
                        speed_factor=0.8
                    )

    return script


def _update_segment_with_new_animation_and_speed(script, segment, new_material, speed_factor):
    """
    更新 segment：添加动画和速度调整，并清理已有的冲突效果。

    Args:
        script (draft.Script): 当前脚本对象
        segment (VideoSegment): 要修改的视频片段对象
        new_material (VideoMaterial): 新的视频素材
        speed_factor (float): 视频播放速度（例如 0.8 表示慢速）
    """
    # 计算时间范围（单位转换：纳秒 -> 秒）
    start_time = (segment.target_timerange.start - 10) / 1000000
    duration = (segment.target_timerange.duration - 10) / 1000000

    # 创建新的视频片段（含速度设置）
    temp_segment = draft.Video_segment(
        new_material,
        target_timerange=trange(f"{start_time}", f"{duration}"),
        speed=speed_factor
    )

    # 随机选择一个入场动画
    random_intro = random.choice(list(Intro_type))
    temp_segment = temp_segment.add_animation(random_intro, duration=f"{duration}s")
    animation_id = temp_segment.animations_instance.animation_id
    logger.debug(f"生成动画ID: {animation_id}")

    # 设置当前 segment 的速度值
    speed_instance = Speed(speed_factor)
    segment.raw_data['speed'] = speed_factor

    # 清理已有冲突的 extra_material_refs（只保留一个动画、一个速度）
    _clean_extra_material_references(segment, script)

    # 添加新的动画和速度引用
    segment.raw_data['extra_material_refs'].append(animation_id)
    segment.raw_data['extra_material_refs'].append(speed_instance.global_id)

    # 将新动画和速度加入全局材料库
    script.materials.speeds.append(speed_instance)
    script.materials.animations.append(temp_segment.animations_instance)


def _clean_extra_material_references(segment, script):
    """
    清理 segment 中多余的 extra_material_refs 引用，防止冲突。

    Args:
        segment (VideoSegment): 要清理的视频片段
        script (draft.Script): 当前脚本对象
    """
    existing_refs = segment.raw_data.get('extra_material_refs', [])

    for ref_id in existing_refs[:]:  # 使用切片避免在遍历时修改列表
        # 动画冲突检测
        for animation in script.imported_materials.get('material_animations', []):
            if animation["id"] == ref_id:
                logger.debug("发现已有动画，移除旧动画引用")
                segment.raw_data['extra_material_refs'].remove(ref_id)

        # 速度冲突检测
        for speed in script.imported_materials.get('speeds', []):
            if speed["id"] == ref_id:
                logger.debug("发现已有速度设置，移除旧速度引用")
                segment.raw_data['extra_material_refs'].remove(ref_id)

        # # 蒙版冲突检测
        # for mask in script.imported_materials.get('masks', []):
        #     if mask["id"] == ref_id:
        #         logger.debug("发现已有蒙版，移除旧蒙版引用")
        #         segment.raw_data['extra_material_refs'].remove(ref_id)
        #
        # # 音轨映射冲突检测
        # for channel_map in script.imported_materials.get('sound_channel_mappings', []):
        #     if channel_map["id"] == ref_id:
        #         logger.debug("发现已有音轨映射，移除旧音轨映射引用")
        #         segment.raw_data['extra_material_refs'].remove(ref_id)
        #
        # # 人声分离冲突检测
        # for vocal_sep in script.imported_materials.get('vocal_separations', []):
        #     if vocal_sep["id"] == ref_id:
        #         logger.debug("发现已有背景音分离效果，移除旧引用")
        #         segment.raw_data['extra_material_refs'].remove(ref_id)
        #
        # # 响度冲突检测
        # for loudness in script.imported_materials.get('loudnesses', []):
        #     if loudness["id"] == ref_id:
        #         logger.debug("发现已有响度调节效果，移除旧引用")
        #         segment.raw_data['extra_material_refs'].remove(ref_id)



def replace_cover(script, cover_file):
    """
    替换剪映模板中的封面图。

    Args:
        script (draft.Script): 当前操作的脚本对象
        cover_file (str): 封面图文件路径
    """
    logger.info("开始替换封面...")
    cover_material = draft.Video_material(cover_file)
    script.replace_material_by_name(material_name="封面.png", material=cover_material)
    logger.info(f"替换【封面】素材: 封面.png -> {cover_file}")


def import_subtitles(script, srt_path):
    """
    导入 SRT 字幕文件到视频轨道中。

    Args:
        script (draft.Script): 当前操作的脚本对象
        srt_path (str): SRT 字幕文件路径
    """
    logger.info("开始导入字幕...")
    try:
        text_segment = draft.Text_segment("文字", trange("0s", "10s"),
                                          font=Font_type.新青年体,
                                          border=draft.Text_border(width=30),
                                          style=Text_style(bold=True, size=12,
                                                           color=(1.0, 1.0, 1.0),
                                                           underline=False,
                                                           align=1, alpha=1.0))
        script.import_srt(srt_path, track_name="字幕2",
                          style_reference=text_segment,
                          clip_settings=draft.Clip_settings(transform_y=-0.5),
                          font_size=10)
        logger.info(f"成功导入SRT文件: {srt_path}")
    except Exception as e:
        logger.error(f"导入SRT失败: {e}", exc_info=True)


# 定义一个函数来生成帧时间偏移
def randomize_frame_timing(duration, fps_base=24, jitter_ratio=0.05):
    """
    在固定总时长内，为每一帧添加轻微随机时间偏移。

    Args:
        duration (float): 视频总时长（秒）
        fps_base (int): 基础帧率
        jitter_ratio (float): 抖动比例（建议 0~0.1）

    Returns:
        list: 每帧对应的时间戳列表
    """
    total_frames = int(duration * fps_base)
    base_interval = 1.0 / fps_base
    times = []

    current_time = 0.0
    for _ in range(total_frames):
        interval_jitter = base_interval * (1 + np.random.uniform(-jitter_ratio, jitter_ratio))
        times.append(current_time)
        current_time += interval_jitter

    # 最后缩放时间轴，保证总长度与原视频一致
    scale_factor = duration / current_time
    times = [t * scale_factor for t in times]
    return times


def crop_video(output_path, audio_duration, export_folder, combo_name):
    """
    根据音频时长裁剪视频。

    Args:
        output_path (str): 视频输出路径
        audio_duration (float): 音频时长（秒）
        export_folder (str): 输出目录
        combo_name (str): 任务名称标识符
    """
    logger.info("开始裁剪视频...")
    accelerated_audio_duration = audio_duration + 1
    video_clip = VideoFileClip(output_path)

    # 裁剪并调整帧率
    if video_clip.duration > accelerated_audio_duration:
        logger.info(f"视频时长 {video_clip.duration:.2f}s 超过音频时长 {accelerated_audio_duration:.2f}s，将进行裁剪")
        video_clip = video_clip.subclipped(0, accelerated_audio_duration)

        # 随机化帧时间轴
        logger.info(f"对视频进行随机帧变化处理")
        duration = video_clip.duration
        times = randomize_frame_timing(duration, fps_base=24, jitter_ratio=0.05)  # 抖动 ±5%

        # 自定义 transform 函数
        def custom_transform(get_frame, t, times_list, total_frames, duration):
            """
            自定义帧映射函数，用于替代原来的 .fl() 逻辑
            """
            # 计算当前 t 应该对应哪个帧索引
            frame_index = min(int(round(t * total_frames / duration)), len(times_list) - 1)
            actual_time = times_list[frame_index]
            return get_frame(actual_time)

        # 构造带参数的 transform 函数
        def make_frame_func(times_list, total_frames, duration):
            def func(get_frame, t):
                return custom_transform(get_frame, t, times_list, total_frames, duration)

            return func

        # 应用 transform
        total_frames = int(duration * 24)  # 根据原始帧率计算总帧数
        new_clip = video_clip.transform(make_frame_func(times, total_frames, duration))

        # 导出视频
        # new_output_path = os.path.join(r"D:\#素材暂存\批量视频\耶稣涨粉-2\0523", "已剪辑", f"剪裁_{combo_name}.mp4")
        new_output_path = os.path.join(r"D:\批量_视频保存\耶稣涨粉-2\0603", f"剪裁_{combo_name}.mp4")
        os.makedirs(os.path.dirname(new_output_path), exist_ok=True)
        new_clip.write_videofile(new_output_path, codec="libx264", audio_codec="aac", fps=24)
    else:
        logger.info("视频时长与音频匹配，无需裁剪")

    video_clip.close()


def delete_old_draft(folder_path, combo_name):
    """
    删除历史草稿文件夹。

    Args:
        folder_path (str): 草稿所在基础路径
        combo_name (str): 要删除的草稿名
    """
    full_path = os.path.join(folder_path, combo_name)
    logger.info(f"尝试删除历史草稿: {full_path}")
    if os.path.exists(full_path):
        shutil.rmtree(full_path)
        logger.info(f"✅ 已删除文件夹: {full_path}")
    else:
        logger.warning(f"⚠️ 文件夹不存在: {full_path}")


# ==================== 主流程 ====================

def jesus_template():
    """
    主函数：批量生成剪映视频并导出
    """

    # 初始化剪映项目配置
    logger.info("初始化剪映草稿模板...")
    draft_folder = draft.Draft_folder(DRAFT_PATH)

    # 设置资源路径
    home_page_dir = r"D:\模板3_耶稣涨粉-2\封面"
    bgm_dir = r"D:\模板3_耶稣涨粉-2\BGM"
    image_dir = r"D:\模板3_耶稣涨粉-2\图片_1"
    audio_dir = r"D:\模板3_耶稣涨粉-2\口播音频"
    srt_dir = r"D:\模板3_耶稣涨粉-2\SRT"
    base_folder = os.path.join(
        # LOCALAPPDATA 是 Windows 系统中的一个环境变量，表示当前用户的本地应用程序数据存储路径
        # C:\Users\<用户名>\AppData\Local
        os.getenv("LOCALAPPDATA"),
        "JianyingPro\\User Data\\Projects\\com.lveditor.draft"
    )

    # 加载资源文件
    logger.info("加载本地资源文件...")
    all_home_files = load_files_from_dir(home_page_dir)
    all_bgm_files = load_files_from_dir(bgm_dir)
    all_image_files = load_files_from_dir(image_dir)

    # 读取 Excel 数据
    logger.info("读取Excel数据...")
    excel_path = r"D:\模板3_耶稣涨粉-2\Jesus_null1.xlsx"
    columns_to_read = ['Number', 'Content', 'Vo_path']
    df = pd.read_excel(excel_path, usecols=columns_to_read)
    data_list = df.to_dict(orient='records')

    # 循环处理每条数据
    logger.info("开始批量生成视频任务...")
    start_ = 310
    end_ = 500
    for k, data in enumerate(data_list[start_:end_]):
        try:
            k += start_
            combo_name = f"{k}_耶稣涨粉_模板_3"
            voice_file_name = os.path.basename(data['Vo_path'])
            srt_file_name = data['Content']

            logger.info(f"🚀 开始处理第 {k} 条任务")
        except  Exception as e:
            logger.error(f"数据处理失败: {e}", exc_info=True)
            continue
        logger.info(f"音频文件: {voice_file_name} | 字幕文件: {srt_file_name}")

        # 创建脚本副本
        script = draft_folder.duplicate_as_template("模板3_耶稣涨粉-3", combo_name, allow_replace=True)
        logger.info(f"创建草稿副本: {combo_name}")

        # 替换 BGM
        replace_bgm(script, random.choice(all_bgm_files))

        # 替换口播音频
        new_audio_path = os.path.join(audio_dir, voice_file_name)
        replace_voiceover(script, new_audio_path)

        # 替换图片
        image_files = random.sample(all_image_files, 20)
        tmp_script = replace_images(script, image_files)

        # 替换封面
        replace_cover(tmp_script, all_home_files[k % len(all_home_files)])

        # 导入字幕
        srt_path = os.path.join(srt_dir, srt_file_name)
        if os.path.exists(srt_path):
            import_subtitles(tmp_script, srt_path)
        else:
            logger.warning(f"未找到字幕文件: {srt_file_name}")

        # 保存草稿
        # print(tmp_script.dumps())
        tmp_script.save()
        logger.info("✅ 草稿保存完成")

        # 导出视频
        logger.info("🎬 开始导出视频")
        ctrl = draft.Jianying_controller()
        export_folder = os.path.join(OUTPUT_PATH, "耶稣涨粉-2")
        output_path = os.path.join(export_folder, f"{combo_name}.mp4")

        ctrl.export_draft(combo_name, output_path,
                          resolution=Export_resolution.RES_1080P,
                          framerate=Export_framerate.FR_24,
                          zi_mu=False,
                          kou_tu=False)
        logger.info(f"导出视频完成: {output_path}")

        # 裁剪视频
        logger.info("✂️ 开始裁剪视频")
        audio_clip = AudioFileClip(new_audio_path)
        audio_duration = audio_clip.duration
        audio_clip.close()
        crop_video(output_path, audio_duration, export_folder, combo_name)

        # 删除旧草稿
        delete_old_draft(base_folder, combo_name)

        logger.info(f"✅ 第 {k} 条任务处理完成\n")

    logger.info("🎉 全部任务已完成！")


if __name__ == "__main__":
    jesus_template()
